#include "FLock.h"
#include "DBChecker.h"

#include <signal.h>
#include <sys/file.h>
#include <unistd.h>

#include <cstdlib>
#include <stdexcept>


namespace{
	const int buff_size = 100;
}

namespace CExpert{
	//TODO XXX use wrappers
	FLock::FLock(const char* conn_str){
		filename_ = std::string("/var/lock/cexpert_") + CExpert::DBChecker::connstr2filename(conn_str);
		const char* filename = filename_.c_str();

		fd_ = open(filename, O_CREAT | O_WRONLY,  S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
		if (fd_ < 0){
			throw std::runtime_error("can't open file");
		}
		if (flock(fd_, LOCK_EX | LOCK_NB) < 0){
			close(fd_);
			throw std::runtime_error("can't flock");
		}
		if (ftruncate(fd_, 0)){
			close(fd_);
			flock(fd_, LOCK_UN);
			throw std::runtime_error("can't ftruncate");
		}
		pid_t current_pid = getpid();
		char buff[buff_size] = {0};
		int written_bytes = snprintf(buff, buff_size, "%d", current_pid);
		if (write(fd_, (void*)buff, written_bytes) != written_bytes){
			close(fd_);
			flock(fd_, LOCK_UN);
			throw std::runtime_error("can't write");
		}
	}
	FLock::~FLock(){
		close(fd_);
		flock(fd_, LOCK_UN);
		//XXX
		unlink(filename_.c_str());
	}

	pid_t FLock::held_by(const char* conn_str){
		std::string lock_filename = std::string("/var/lock/cexpert_") + CExpert::DBChecker::connstr2filename(conn_str);
		const char* filename = lock_filename.c_str();

		int fd = open(filename, O_RDONLY, 0);
		if (fd < 0){
			return -1;
		}
		if (flock(fd, LOCK_EX | LOCK_NB) == 0){
			flock(fd, LOCK_UN);
			close(fd);
			return -1;
		}
		//can't held lock - read owner's pid
		char buff[buff_size] = {0};
		ssize_t read_bytes = read(fd, buff, buff_size);
		if (read_bytes > 0){
			close(fd);
			pid_t holder_pid = atoi(buff);
			//check for pid is alive
			if (kill(holder_pid, 0) != 0)
				//probably, the holder was die
				return -1;
			return holder_pid;
		}
		close(fd);
		throw std::runtime_error("can't read lock's owner pid");
	}

	bool FLock::kill_holder(const char* conn_str){
		pid_t holder_pid = held_by(conn_str);
		if (holder_pid < 0)
			return true;
		return (kill(holder_pid, SIGINT) == 0);
	}
}
